﻿
namespace HSS.Xaml.UI.Controls
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Linq;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Input;
	using System.Windows.Media;
	using HSS.ComponentModel;
	using HSS.Resources;
	using HSS.Xaml.UI.Media;
	#endregion

	#region PropertyGrid
	/// <summary>
	/// PropertyGrid
	/// </summary>
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), TemplatePart(Name = PART_ToolBar, Type = typeof(Border))]
	[TemplatePart(Name = PART_MainBorder, Type = typeof(Border))]
	[TemplatePart(Name = PART_ToolbarCategorizeButton, Type = typeof(ToggleButton))]
	[TemplatePart(Name = PART_ToolbarSortButton, Type = typeof(ToggleButton))]
	[TemplatePart(Name = PART_ToolbarFilterTextBox, Type = typeof(TextBox))]
	[TemplatePart(Name = PART_ToolbarClearButton, Type = typeof(Button))]
	[TemplatePart(Name = PART_ViewPort, Type = typeof(Grid))]
	[TemplatePart(Name = PART_MainGrid, Type = typeof(Grid))]
	[TemplatePart(Name = PART_MainScrollBar, Type = typeof(ScrollBar))]
	[TemplatePart(Name = PART_SelectedPropertyName, Type = typeof(TextBlock))]
	[TemplatePart(Name = PART_SelectedPropertyDescription, Type = typeof(TextBlock))]
	public sealed class PropertyGrid : ContentControl
	{
		#region Part Names
		/// <summary>
		/// The name of the Toolbar part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		public const string PART_ToolBar = "Toolbar";
		/// <summary>
		/// The name of the MainBorder part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		public const string PART_MainBorder = "MainBorder";
		/// <summary>
		/// The name of the ViewPort part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ViewPort")]
		public const string PART_ViewPort = "ViewPort";
		/// <summary>
		/// The name of the ToolbarCategorizeButton part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public const string PART_ToolbarCategorizeButton = "ToolbarCategorizeButton";
		/// <summary>
		/// The name of the ToolbarSortButton part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public const string PART_ToolbarSortButton = "ToolbarSortButton";
		/// <summary>
		/// The name of the ToolbarFilterTextBox part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public const string PART_ToolbarFilterTextBox = "ToolbarFilterTextBox";
		/// <summary>
		/// The name of the ToolbarClearButton part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public const string PART_ToolbarClearButton = "ToolbarClearButton";
		/// <summary>
		/// The name of the MainGrid part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		public const string PART_MainGrid = "MainGrid";
		/// <summary>
		/// The name of the MainScrollBar part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		public const string PART_MainScrollBar = "MainScrollBar";
		/// <summary>
		/// The name of the SelectedPropertyName part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		public const string PART_SelectedPropertyName = "SelectedPropertyName";
		/// <summary>
		/// The name of the SelectedPropertyDescription part
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PART")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
		public const string PART_SelectedPropertyDescription = "SelectedPropertyDescription";

		const string PART_MainDescription = "MainDescription";
		const string PART_MainDescriptionRow = "MainDescriptionRow";
		const string PART_MainSplitter = "MainSplitter";
		const string PART_MainSplitterRow = "MainSplitterRow";

		#endregion

		#region Events
		/// <summary>
		/// Fired when a property grid entry value is modified.
		/// </summary>
		/// <remarks>The sender is the <see cref="HSSPropertyDescriptor"/> for the property being modified.</remarks>
		public event EventHandler<EventArgs> ValueChanged;
		private void OnValueChanged(object sender, EventArgs e)
		{
			var handler = ValueChanged;
			if (null != handler)
				handler(sender, e);
		}
		#endregion

		#region Fields

		// PropertyGridHeader RowHeight
		const int rowHeight = 21;

		Dictionary<int, IPropertyGridItem> gridItems = new Dictionary<int, IPropertyGridItem>();
		IPropertyGridItem selectedItem;
		RectangleGeometry viewPortClip;
		GridSplitter mainGridSplitter;
		PropertyDescriptorCollection propertyList;

		string currentFilter = string.Empty;

		bool sortAZ;
		bool isSorted;
		bool isViewPortClipSet;
		bool templateApplied;

		int selectedIndex = 0;

		Border MainDescription;
		RowDefinition MainDescriptionRow;
		GridSplitter MainSplitter;
		RowDefinition MainSplitterRow;

		ColumnDefinition gridLabelColumnDefinition;

		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public PropertyGrid()
		{
			base.DefaultStyleKey = typeof(PropertyGrid);
			if (DesignerProperties.IsInDesignTool)
				return;
			this.Loaded += new RoutedEventHandler(PropertyGrid_Loaded);
			this.Unloaded += new RoutedEventHandler(PropertyGrid_Unloaded);
		}
		#endregion

		#region Properties

		#region SelectedObject
		/// <summary>
		/// Gets or sets the Object whose properties are to be viewed and 
		/// optionally edited if the individual property supporting editing.
		/// </summary>
		[Category("Common")]
		[Description("Gets or sets the Object whose properties are to be viewed.")]
		public object SelectedObject
		{
			get { return base.GetValue(SelectedObjectProperty); }
			set { base.SetValue(SelectedObjectProperty, value); }
		}
		/// <summary>
		/// The SelectedObject DependencyProperty
		/// </summary>
		public static readonly DependencyProperty SelectedObjectProperty =
		  DependencyProperty.Register("SelectedObject", typeof(object), typeof(PropertyGrid), new PropertyMetadata(null, OnSelectedObjectChanged));
		private static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var propertyGrid = d as PropertyGrid;
			if (propertyGrid != null)
			{
				if (!propertyGrid.templateApplied)
					return;

				bool sameType = false;
				if (null != e.NewValue && null != e.OldValue)
					sameType = e.NewValue.GetType() == e.OldValue.GetType();
				propertyGrid.ResetObject(sameType ? propertyGrid.selectedIndex : 0, propertyGrid.MainScrollBar.Value);
				INotifyPropertyChanged oldValue = e.OldValue as INotifyPropertyChanged;
				if (oldValue != null)
				{
					oldValue.PropertyChanged -= new PropertyChangedEventHandler(propertyGrid.Object_PropertyChanged);
				}
				INotifyPropertyChanged newValue = e.NewValue as INotifyPropertyChanged;
				if (newValue != null)
				{
					newValue.PropertyChanged += new PropertyChangedEventHandler(propertyGrid.Object_PropertyChanged);
				}
			}
		}
		private void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			IPropertyGridItem item = this.gridItems.Values.FirstOrDefault<IPropertyGridItem>(p => p.Name == e.PropertyName);
			if (item != null)
				item.Refresh();
			//this.sawObjectPropertyChange = true;
		}


		#endregion

		#region DefaultLabelWidth
		/// <summary>
		/// Gets or sets the Default Width for the labels
		/// </summary>
		public GridLength DefaultLabelWidth
		{
			get { return (GridLength)base.GetValue(DefaultLabelWidthProperty); }
			set { base.SetValue(DefaultLabelWidthProperty, value); }
		}
		/// <summary>
		/// The DefaultLabelWidth DependencyProperty
		/// </summary>
		public static readonly DependencyProperty DefaultLabelWidthProperty =
		  DependencyProperty.Register("DefaultLabelWidth", typeof(GridLength), typeof(PropertyGrid), new PropertyMetadata(new GridLength(100), OnDefaultLabelWidthChanged));
		private static void OnDefaultLabelWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var propertyGrid = d as PropertyGrid;
			if (propertyGrid != null)
				propertyGrid.UpdateDefaultLabelWidth((GridLength)e.NewValue);
		}
		void UpdateDefaultLabelWidth(GridLength newValue)
		{
			if (null != this.gridLabelColumnDefinition)
				this.gridLabelColumnDefinition.Width = newValue;
		}
		#endregion

		#region DescriptionBorderThickness
		/// <summary>
		/// Gets or sets the Border Thickness of the Property Grid
		/// </summary>
		public Thickness DescriptionBorderThickness
		{
			get { return (Thickness)base.GetValue(DescriptionBorderThicknessProperty); }
			set { base.SetValue(DescriptionBorderThicknessProperty, value); }
		}
		/// <summary>
		/// The DescriptionBorderThickness DependencyProperty
		/// </summary>
		public static readonly DependencyProperty DescriptionBorderThicknessProperty =
			DependencyProperty.Register("DescriptionBorderThickness", typeof(Thickness), typeof(PropertyGrid), new PropertyMetadata(new Thickness(1)));
		#endregion

		#region ToolbarBorderThickness
		/// <summary>
		/// Gets or sets the Border Thickness of the Property Toolbar
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public Thickness ToolbarBorderThickness
		{
			get { return (Thickness)base.GetValue(ToolbarBorderThicknessProperty); }
			set { base.SetValue(ToolbarBorderThicknessProperty, value); }
		}
		/// <summary>
		/// The ToolbarBorderThickness DependencyProperty
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public static readonly DependencyProperty ToolbarBorderThicknessProperty =
			DependencyProperty.Register("ToolbarBorderThickness", typeof(Thickness), typeof(PropertyGrid), new PropertyMetadata(new Thickness(1, 1, 1, 0)));
		#endregion

		#region Parts
		/// <summary>
		/// Gets the Toolbar (Border) for the PropertyGrid control
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Toolbar")]
		public Border Toolbar
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the Main border for the PropertyGrid control
		/// </summary>
		public Border MainBorder
		{
			get { return _mainBorder; }
			private set
			{
				if (null != _mainBorder)
					_mainBorder.SizeChanged -= new SizeChangedEventHandler(MainBorder_SizeChanged);
				_mainBorder = value;
				if (DesignerProperties.IsInDesignTool)
					return;
				if (null != _mainBorder)
					_mainBorder.SizeChanged += new SizeChangedEventHandler(MainBorder_SizeChanged);
			}
		} Border _mainBorder;
		/// <summary>
		/// Gets the ViewPort for the PropertyGrid items
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ViewPort")]
		public Grid ViewPort
		{
			get { return _viewPort; }
			private set
			{
				_viewPort = value;
			}
		} Grid _viewPort;
		/// <summary>
		/// Gets the Main grid for the PropertyGrid items
		/// </summary>
		public Grid MainGrid
		{
			get { return _mainGrid; }
			private set
			{
				if (null != _mainGrid)
					_mainGrid.LayoutUpdated -= new EventHandler(MainGrid_LayoutUpdated);
				_mainGrid = value;
				if (DesignerProperties.IsInDesignTool)
					return;
				if (null != _mainGrid)
					_mainGrid.LayoutUpdated += new EventHandler(MainGrid_LayoutUpdated);
			}
		} Grid _mainGrid;
		/// <summary>
		/// Gets the Categorize Button
		/// </summary>
		public ToggleButton CategorizeButton
		{
			get { return _categorizeButton; }
			private set
			{
				if (null != _categorizeButton)
					_categorizeButton.Click -= new RoutedEventHandler(CategorizeButton_Click);
				_categorizeButton = value;
				if (DesignerProperties.IsInDesignTool)
					return;
				if (null != _categorizeButton)
					_categorizeButton.Click += new RoutedEventHandler(CategorizeButton_Click);
			}

		} ToggleButton _categorizeButton;
		/// <summary>
		/// Gets the Sort Button
		/// </summary>
		public ToggleButton SortButton
		{
			get { return _sortButton; }
			private set
			{
				if (null != _sortButton)
					_sortButton.Click -= new RoutedEventHandler(SortButton_Click);
				_sortButton = value;
				if (DesignerProperties.IsInDesignTool)
					return;
				if (null != _sortButton)
					_sortButton.Click += new RoutedEventHandler(SortButton_Click);
			}

		} ToggleButton _sortButton;
		/// <summary>
		/// Gets the Filter TextBox
		/// </summary>
		public TextBox FilterTextBox
		{
			get { return _filterTextBox; }
			private set
			{
				if (null != _filterTextBox)
					_filterTextBox.MouseLeftButtonDown -= new MouseButtonEventHandler(FilterTextBox_MouseLeftButtonDown);
				_filterTextBox = value;
				if (DesignerProperties.IsInDesignTool)
					return;
				if (null != _filterTextBox)
					_filterTextBox.MouseLeftButtonDown += new MouseButtonEventHandler(FilterTextBox_MouseLeftButtonDown);
			}
		} TextBox _filterTextBox;
		/// <summary>
		/// Gets the Clear filter
		/// </summary>
		public Button ClearButton
		{
			get { return _clearButton; }
			private set
			{
				if (null != _clearButton)
					_clearButton.Click -= new RoutedEventHandler(ClearButton_Click);
				_clearButton = value;
				if (DesignerProperties.IsInDesignTool)
					return;
				if (null != _clearButton)
					_clearButton.Click += new RoutedEventHandler(ClearButton_Click);
			}

		} Button _clearButton;
		/// <summary>
		/// Gets the Scrollbar for the PropertyGrid items
		/// </summary>
		public ScrollBar MainScrollBar
		{
			get { return _mainScrollBar; }
			private set
			{
				if (null != _mainScrollBar)
					_mainScrollBar.ValueChanged -= new RoutedPropertyChangedEventHandler<double>(MainScrollBar_ValueChanged);
				_mainScrollBar = value;
				if (null != _mainScrollBar)
				{
					_mainScrollBar.Width = 5;
					if (DesignerProperties.IsInDesignTool)
						return;
					_mainScrollBar.ValueChanged += new RoutedPropertyChangedEventHandler<double>(MainScrollBar_ValueChanged);
				}
			}
		} ScrollBar _mainScrollBar;
		/// <summary>
		/// Gets the SelectProperty Name textblock for the PropertyGrid control
		/// </summary>
		public TextBlock SelectedPropertyName
		{
			get { return _selectedPropertyName; }
			private set
			{
				_selectedPropertyName = value;
			}
		} TextBlock _selectedPropertyName;
		/// <summary>
		/// Gets the SelectProperty Description textblock for the PropertyGrid control
		/// </summary>
		public TextBlock SelectedPropertyDescription
		{
			get { return _selectedPropertyDescription; }
			private set
			{
				_selectedPropertyDescription = value;
			}
		} TextBlock _selectedPropertyDescription;
		#endregion

		//#region Services
		///// <summary>
		///// Gets the PropertyGrid's Services <see cref="ServiceContainer"/>
		///// </summary>
		//public ServiceContainer Services { get; private set; }
		//#endregion

		#region IsDescriptionVisible
		/// <summary>
		/// Gets or sets if the Description text is visible.
		/// </summary>
		public bool IsDescriptionVisible
		{
			get { return (bool)GetValue(IsDescriptionVisibleProperty); }
			set { SetValue(IsDescriptionVisibleProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for IsDescriptionVisible.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty IsDescriptionVisibleProperty =
			DependencyProperty.Register("IsDescriptionVisible", typeof(bool), typeof(PropertyGrid), new PropertyMetadata(true, OnIsDescriptionVisibleChanged));
		private static void OnIsDescriptionVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var propertyGrid = d as PropertyGrid;
			if (propertyGrid != null && null != e.NewValue)
				propertyGrid.UpdateIsDescriptionVisible((bool)e.NewValue);
		}
		private void UpdateIsDescriptionVisible(bool isVisible)
		{
			if (null == this.MainDescription)
				return;
			if (null == this.MainDescriptionRow)
				return;
			this.MainDescriptionRow.MinHeight = isVisible ? 60 : 0;
			this.MainDescriptionRow.Height = isVisible ? new GridLength(60) : new GridLength(0);
			this.MainDescription.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
			if (null != this.MainSplitter)
				this.MainSplitter.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
			if (null != this.MainSplitterRow)
				this.MainSplitterRow.Height = isVisible ? new GridLength(3) : new GridLength(0);
		}
		#endregion

		[Browsable(false)]
		internal PropertyGridEditor ActiveEditor { get; set; }

		[Browsable(false)]
		internal bool ForceReadOnly { get; set; }

		#endregion

		#region Overrides
		/// <summary>
		/// Apply the Control Template.
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			this.TabNavigation = KeyboardNavigationMode.Cycle;
			this.Toolbar = this.GetTemplateChild(PropertyGrid.PART_ToolBar) as Border;
			this.MainBorder = this.GetTemplateChild(PropertyGrid.PART_MainBorder) as Border;
			this.ViewPort = this.GetTemplateChild(PropertyGrid.PART_ViewPort) as Grid;
			this.CategorizeButton = this.GetTemplateChild(PropertyGrid.PART_ToolbarCategorizeButton) as ToggleButton;
			this.SortButton = this.GetTemplateChild(PropertyGrid.PART_ToolbarSortButton) as ToggleButton;
			this.FilterTextBox = this.GetTemplateChild(PropertyGrid.PART_ToolbarFilterTextBox) as TextBox;
			this.ClearButton = this.GetTemplateChild(PropertyGrid.PART_ToolbarClearButton) as Button;
			this.MainGrid = this.GetTemplateChild(PropertyGrid.PART_MainGrid) as Grid;
			this.MainScrollBar = this.GetTemplateChild(PropertyGrid.PART_MainScrollBar) as ScrollBar;
			this.SelectedPropertyName = this.GetTemplateChild(PropertyGrid.PART_SelectedPropertyName) as TextBlock;
			this.SelectedPropertyDescription = this.GetTemplateChild(PropertyGrid.PART_SelectedPropertyDescription) as TextBlock;

			this.MainDescription = this.GetTemplateChild(PropertyGrid.PART_MainDescription) as Border;
			this.MainDescriptionRow = this.GetTemplateChild(PropertyGrid.PART_MainDescriptionRow) as RowDefinition;
			this.MainSplitter = this.GetTemplateChild(PropertyGrid.PART_MainSplitter) as GridSplitter;
			this.MainSplitterRow = this.GetTemplateChild(PropertyGrid.PART_MainSplitterRow) as RowDefinition;
			this.UpdateIsDescriptionVisible(this.IsDescriptionVisible);

			this.gridLabelColumnDefinition = this.GetTemplateChild("gridLabelColumnDefinition") as ColumnDefinition;
			this.UpdateDefaultLabelWidth(this.DefaultLabelWidth);

			this.templateApplied = true;
			if (DesignerProperties.IsInDesignTool)
				return;
			this.ResetObject(0, 0);
		}
		/// <summary>
		/// Process the KeyDown action.
		/// </summary>
		/// <param name="e">The KeyEventArgs args.</param>
		protected override void OnKeyDown(KeyEventArgs e)
		{
			Guard.ArgumentNotNull(e, "e");
			if (null != this.selectedItem && this.selectedItem.IsEditorFocused)
				return;

			#region Handle Control Modifier

			// Change the width of the Labels
			if (Keyboard.Modifiers == ModifierKeys.Control)
			{
				double currentWidth = this.MainGrid.ColumnDefinitions[1].ActualWidth;
				double currentMinWidth = this.MainGrid.ColumnDefinitions[1].MinWidth;
				double lastColMinWidth = this.MainGrid.ColumnDefinitions[2].MinWidth;
				if (lastColMinWidth < 50)
					lastColMinWidth = 50;

				if (e.Key == Key.Left)
				{
					currentWidth = currentWidth - 10;
					if (currentWidth < currentMinWidth)
						currentWidth = currentMinWidth;
					this.MainGrid.ColumnDefinitions[1].Width = new GridLength(currentWidth);

				}
				if (e.Key == Key.Right)
				{
					currentWidth = currentWidth + 10;
					if (currentWidth > (this.viewPortClip.Rect.Width - lastColMinWidth))
						currentWidth = (this.viewPortClip.Rect.Width - lastColMinWidth);
					this.MainGrid.ColumnDefinitions[1].Width = new GridLength(currentWidth);
				}
				return;
			}
			#endregion

			#region Handle Up, Down, Left, Right
			if (e.Key == Key.Up)
			{
				if (this.selectedIndex >= 0)
				{
					this.ActivateItem(true);
					e.Handled = true;
					return;
				}
			}
			if (e.Key == Key.Left)
			{
				if (this.selectedIndex >= 0)
				{
					this.ActivateOrToggleIfHeader(true);
					e.Handled = true;
					return;
				}
			}
			if (e.Key == Key.Down)
			{
				if (this.selectedIndex < this.gridItems.Count)
				{
					this.ActivateItem(false);
					e.Handled = true;
					return;
				}
			}
			if (e.Key == Key.Right)
			{
				if (this.selectedIndex < this.gridItems.Count)
				{
					this.ActivateOrToggleIfHeader(false);
					e.Handled = true;
					return;
				}
			}
			#endregion

			base.OnKeyDown(e);
		}
		#endregion

		#region Methods
		void ResetObject(int currentIndex, double scrollValue)
		{
			this.currentFilter = string.Empty;
			this.selectedIndex = -1;
			this.selectedItem = null;
			if (null != propertyList)
			{
				propertyList.Clear();
				propertyList = null;
			}

			this.ResetMainGrid();

			object instance = this.SelectedObject;
			if (null == instance)
			{
				this.FilterTextBox.IsEnabled = false;
				this.ClearButton.IsEnabled = false;
				return;
			}

			this.propertyList = TypeDescriptor.GetProperties(instance);

			if (this.isSorted)
				this.propertyList = this.propertyList.Sort(HSSPropertyDescriptor.Comparer);

			int rowCount = this.RenderProperties();

			if (rowCount > 0)
			{
				this.AddGridSplitter(rowCount);
				this.FilterTextBox.IsEnabled = true;
				this.ClearButton.IsEnabled = true;
			}

			this.Dispatcher.BeginInvoke(delegate()
			{
				this.SelectItem(GetItem(currentIndex));
				this.MainScrollBar.Value = scrollValue;
				this.ResetFilterTextBox();
			});

		}
		void ResetMainGrid()
		{
			if (null != gridItems)
			{
				foreach (var item in gridItems)
					item.Value.OnRemoved();
				gridItems.Clear();
				gridItems = null;
			}
			gridItems = new Dictionary<int, IPropertyGridItem>();

			if (null != MainGrid)
			{
				this.MainGrid.Children.Clear();
				this.MainGrid.RowDefinitions.Clear();
			}
		}
		void ResetFilterTextBox()
		{
			this.FilterTextBox.TextChanged -= new TextChangedEventHandler(FilterTextBox_TextChanged);
			this.FilterTextBox.Text = ResourceProvider.GetString(StringKeys.Search);
			this.FilterTextBox.Foreground = new SolidColorBrush(KnownColor.DimGray);
			this.FilterTextBox.FontStyle = FontStyles.Italic;
			this.FilterTextBox.IsTabStop = false;
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void ResetViewPort()
		{
			try
			{
				if (!this.isViewPortClipSet)
				{
					this.viewPortClip = new RectangleGeometry();
					this.ViewPort.Clip = this.viewPortClip;
					this.isViewPortClipSet = true;
				}
				this.viewPortClip.Rect = new Rect(0.0, 0.0, this.MainBorder.RenderSize.Width, this.MainBorder.RenderSize.Height);
			}
			catch (Exception ex)
			{
				if (System.Diagnostics.Debugger.IsAttached)
				{
					System.Diagnostics.Debug.Assert(false, "Why did this happen?");
					System.Diagnostics.Debug.WriteLine(ex.ToString());
				}
			}
		}

		int RenderProperties()
		{
			int rowCount = -1;

			if (this.isSorted)
			{
				foreach (var descriptor in this.propertyList)
				{
					if (this.ForceReadOnly)
						descriptor.ForceSetReadOnly(true);
					this.AddEntryItem(descriptor, ref rowCount);
				}
			}
			else
			{
				var categories = (from p in this.propertyList
								  orderby p.Category
								  select p.Category).Distinct();

				foreach (string category in categories)
				{

					var descriptors = from p in this.propertyList
									  where string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)
									  orderby p.Name
									  select p;

					if (descriptors.Count() > 0)
					{
						this.AddHeaderItem(category, ref rowCount);
						foreach (var descriptor in descriptors)
						{
							if (this.ForceReadOnly)
								descriptor.ForceSetReadOnly(true);
							this.AddEntryItem(descriptor, ref rowCount);
						}
					}
				}
			}

			this.AddGhostBottomItem(ref rowCount);

			return rowCount++;
		}

		void AddHeaderItem(string category, ref int rowIndex)
		{
			#region Create Display Object

			var header = new PropertyGridHeader()
			{
				HeaderLabelText = category,
				ExpandImageSource = ResourceProvider.GetBitmap("assets/plus.png", "SLG50"),
				CollapseImageSource = ResourceProvider.GetBitmap("assets/minus.png", "SLG50"),
				IsSelected = false,
				IsVisible = !this.isSorted,
				PropertyGrid = this
			};
			var brd = new Border
			{
				Background = this.Background,
				Child = header
			};

			#endregion

			#region Update State
			rowIndex++;
			this.MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(rowHeight) });
			header.RowIndex = rowIndex;
			gridItems.Add(rowIndex, header);
			#endregion

			#region Column 0, 1 & 2 - Category Header
			this.MainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumn(brd, 0);
			Grid.SetColumnSpan(brd, 3);
			#endregion
		}
		void AddEntryItem(HSSPropertyDescriptor descriptor, ref int rowIndex)
		{
			Guard.ArgumentNotNull(descriptor, "descriptor");

			#region Create Display Object

			var entry = new PropertyGridEntry
				{
					Descriptor = descriptor,
					Name = descriptor.Name,
					DisplayName = descriptor.DisplayName,
					Description = descriptor.Description,
					Category = descriptor.Category,
					Instance = this.SelectedObject,
					PropertyGrid = this,
				};

			if (!entry.Initialize())
			{
				entry.OnRemoved();
				entry = null;
				return;
			}

			entry.LabelControl.DoubleClick += new RoutedEventHandler(PropertyGridLabel_DoubleClick);

			#endregion

			#region Update State
			rowIndex++;
			this.MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) });
			entry.RowIndex = rowIndex;
			gridItems.Add(rowIndex, entry);
			#endregion

			#region Column 0 - Margin
			this.MainGrid.Children.Add(entry.MarginColumn);
			Grid.SetRow(entry.MarginColumn, rowIndex);
			Grid.SetColumn(entry.MarginColumn, 0);
			#endregion

			#region Column 1 - Label
			this.MainGrid.Children.Add(entry.LabelControl);
			Grid.SetRow(entry.LabelControl, rowIndex);
			Grid.SetColumn(entry.LabelControl, 1);
			#endregion

			#region Column 2 - Editor
			this.MainGrid.Children.Add(entry.ValueControl);
			Grid.SetRow(entry.ValueControl, rowIndex);
			Grid.SetColumn(entry.ValueControl, 2);
			#endregion
		}

		void AddGhostBottomItem(ref int rowIndex)
		{
			rowIndex++;
			this.MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(rowHeight) });

			Border brd = new Border()
			{
				Margin = new Thickness(0),
				BorderThickness = new Thickness(0),
				Background = new SolidColorBrush(Colors.Transparent)
			};
			this.MainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumnSpan(brd, 2);

			// Final row
			rowIndex++;

		}
		void AddGridSplitter(int rowCount)
		{
			if (null != mainGridSplitter)
				mainGridSplitter = null;
			mainGridSplitter = new GridSplitter()
			{
				IsTabStop = false,
				HorizontalAlignment = HorizontalAlignment.Left,
				VerticalAlignment = VerticalAlignment.Stretch,
				Background = new SolidColorBrush(Colors.Transparent),
				ShowsPreview = false,
				Width = 2
			};
			Grid.SetColumn(mainGridSplitter, 2);
			Grid.SetRowSpan(mainGridSplitter, rowCount - 1);
			this.MainGrid.Children.Add(mainGridSplitter);

		}

		IPropertyGridItem GetItem(int index)
		{
			IPropertyGridItem item;
			gridItems.TryGetValue(index, out item);
			return item;
		}
		IPropertyGridItem GetPreviousVisibleItem()
		{
			IPropertyGridItem item = null;
			int index = this.selectedIndex - 1;
			while (index >= 0)
			{
				item = this.GetItem(index);
				if (!item.IsVisible)
					index = index - 1;
				else
					return item;
			}

			return null;
		}
		IPropertyGridItem GetNextVisibleItem()
		{
			IPropertyGridItem item = null;
			int index = this.selectedIndex + 1;
			while (index < this.gridItems.Count)
			{
				item = this.GetItem(index);
				if (!item.IsVisible)
					index = index + 1;
				else
					return item;
			}

			return null;
		}

		internal void ToggleVisibility(bool show, string category)
		{

			var query = from item in this.gridItems.Values
						where item.Category == category && !(item is IPropertyGridHeader)
						select item;

			if (this.currentFilter.Length > 0)
			{
				if (show)
				{
					foreach (var item in query)
					{
						if (item.DisplayName.Contains(this.currentFilter))
							item.IsVisible = true;
						else
							item.IsVisible = false;
					}
				}
				else
				{
					foreach (var item in query)
						item.IsVisible = false;
				}
			}
			else
			{
				foreach (var item in query)
					item.IsVisible = show;
			}
		}
		internal void SelectItem(IPropertyGridItem item)
		{
			if (null == item)
				return;
			var pge = item as PropertyGridEntry;

			if (null != this.selectedItem)
			{
				if (this.selectedItem != item)
					this.selectedItem.Deselect();
				if (null != pge)
					pge.Descriptor.ValueChanged -= new EventHandler<EventArgs>(OnValueChanged);
			}

			this.selectedIndex = item.RowIndex;
			this.selectedItem = item;
			this.SelectedPropertyName.Text = item.DisplayName;
			this.SelectedPropertyDescription.Text = item.Description;
			this.EnsureVisible();

			if (null != pge)
				pge.Descriptor.ValueChanged += new EventHandler<EventArgs>(OnValueChanged);

			item.Select();

		}

		void ActivateItem(bool up)
		{
			IPropertyGridItem item = null;

			if (up)
				item = this.GetPreviousVisibleItem();
			else
				item = this.GetNextVisibleItem();

			this.ActivateItem(item);
		}
		void ActivateItem(int index)
		{
			lock (this)
			{
				IPropertyGridItem entry = GetItem(index);
				this.ActivateItem(entry);
			}
		}
		void ActivateItem(IPropertyGridItem item)
		{
			if (null == item)
			{
				this.EnsureVisible();
				return;
			}

			item.Activate();
		}
		void ActivateOrToggleIfHeader(bool up)
		{
			if (null == this.selectedItem)
			{
				this.ActivateItem(0);
				return;
			}

			if (this.selectedItem is PropertyGridEntry)
			{
				this.ActivateItem(up);
				return;
			}

			if (this.selectedItem is PropertyGridHeader)
			{
				IPropertyGridHeader header = this.selectedItem as PropertyGridHeader;
				bool collapsed = up, expand = !up;
				if (collapsed && header.IsExpanded)
				{
					header.IsExpanded = false;
					return;
				}
				else if (expand && !header.IsExpanded)
				{
					header.IsExpanded = true;
					return;
				}
				else
					this.ActivateItem(up);
			}
		}

		void SetScrollBar()
		{
			#region Get the visible row height
			double height = 0;
			foreach (var child in this.gridItems.Values)
			{
				if (child.IsVisible)
				{
					RowDefinition row = this.MainGrid.RowDefinitions[child.RowIndex];
					height += row.ActualHeight;
				}
			}
			#endregion

			if (height > this.MainBorder.ActualHeight)
			{
				if (!this.isViewPortClipSet)
					this.ResetViewPort();

				if (this.MainScrollBar.Visibility == Visibility.Collapsed ||
					this.MainScrollBar.Maximum != (this.MainGrid.ActualHeight - this.viewPortClip.Rect.Height))
				{
					this.MainScrollBar.Visibility = Visibility.Visible;
					this.MainScrollBar.ViewportSize = this.MainBorder.ActualHeight;
					this.MainScrollBar.Maximum = this.MainGrid.ActualHeight - this.viewPortClip.Rect.Height;
					this.MainScrollBar.Minimum = 0;
					this.MainScrollBar.SmallChange = rowHeight;
					this.MainScrollBar.LargeChange = rowHeight * 6;
				}
			}
			else
			{
				this.MainScrollBar.Visibility = Visibility.Collapsed;
				this.MainScrollBar.Value = 0;
			}
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void EnsureVisible()
		{

			if (null == this.selectedItem)
				return;

			try
			{
				FrameworkElement element = null;

				if (this.selectedItem is PropertyGridHeader)
					element = this.selectedItem as PropertyGridHeader;

				if (this.selectedItem is PropertyGridEntry)
					element = ((PropertyGridEntry)this.selectedItem).LabelControl as PropertyGridLabelControl;

				if (null == element)
					return;

				if (!this.isViewPortClipSet)
					this.ResetViewPort();

				double offset = 3;

				double eleTop, eleBottom;
				GetElementTopBottom(element, out eleTop, out eleBottom);

				double vpTop, vpBottom;
				GetViewPortTopBottom(out vpTop, out vpBottom);

				bool topVisible = false;
				if (eleTop > vpTop)
					topVisible = true;

				bool bottomVisible = false;
				if ((eleBottom + offset) < vpBottom)
					bottomVisible = true;

				if (!topVisible && bottomVisible)
				{
					// scrollup
					double diff = vpTop - eleTop;

					if (eleBottom < 0)
					{
						// Off the screen, scroll to the top of the item
						diff = vpTop - eleTop;
					}
					else
					{
						diff = vpTop - eleTop;
						if (diff > this.MainScrollBar.SmallChange)
							diff = this.MainScrollBar.SmallChange;
					}
					this.MainScrollBar.Value -= diff;
					return;
				}

				if (topVisible && !bottomVisible)
				{
					// scrolldown
					double diff = eleBottom - vpBottom;
					if (eleTop > vpBottom)
					{
						// Off the screen, need to scrolldown more...
						diff = eleBottom - vpBottom;
						diff += this.MainScrollBar.SmallChange;
					}
					else
					{
						diff = eleBottom - vpBottom;
						if (diff > this.MainScrollBar.SmallChange)
							diff = this.MainScrollBar.SmallChange;
						diff += this.MainScrollBar.SmallChange;
					}
					this.MainScrollBar.Value += diff;
					return;
				}
			}
			catch { }
		}
		void ToggleSort()
		{
			this.ResetObject(0, 0);
		}
		void FilterList(string filter)
		{
			this.currentFilter = filter;
			bool isExpanded = false;
			foreach (var item in this.gridItems.Values)
			{
				var pgh = item as IPropertyGridHeader;
				if (null != pgh)
				{
					isExpanded = pgh.IsExpanded;
					continue;
				}
				if (!item.DisplayName.ToUpperInvariant().Contains(filter.ToUpperInvariant()))
					item.IsVisible = false;
				else if (!item.IsVisible && (isExpanded || isSorted))
					item.IsVisible = true;
			}
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void GetViewPortTopBottom(out double top, out double bottom)
		{
			top = bottom = 0;

			try
			{
				var viewPortRect = this.viewPortClip.Rect;

				var trsf = this.ViewPort.TransformToVisual(this.MainBorder);
				var pt = trsf.Transform(new Point(0, 0));
				viewPortRect.Y = pt.Y;
				viewPortRect.X = pt.X;

				top = viewPortRect.Top;
				bottom = viewPortRect.Bottom;
			}
			catch { }
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void GetElementTopBottom(FrameworkElement element, out double top, out double bottom)
		{
			top = bottom = 0;
			try
			{
				var trsf = element.TransformToVisual(this.MainBorder);
				var pt = trsf.Transform(new Point(0, 0));
				var eleRect = new Rect(pt, element.RenderSize);

				top = eleRect.Top;
				bottom = eleRect.Bottom;
			}
			catch { }
		}

		#endregion

		#region Event Handlers
		void PropertyGrid_Loaded(object sender, RoutedEventArgs e)
		{
			this.MouseWheel += new MouseWheelEventHandler(PropertyGrid_MouseWheel);
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void PropertyGrid_Unloaded(object sender, RoutedEventArgs e)
		{
			this.MouseWheel -= new MouseWheelEventHandler(PropertyGrid_MouseWheel);
			if (null != gridItems)
			{
				foreach (var item in gridItems.Values)
				{
					try { item.OnRemoved(); }
					catch { }
				}
			}
		}
		void PropertyGrid_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
		{
			this.Dispatcher.BeginInvoke(delegate()
			{
				double mouseDelta = e.Delta;
				mouseDelta = Math.Sign(mouseDelta);
				mouseDelta = mouseDelta * (this.MainScrollBar.SmallChange * -1);
				mouseDelta = this.MainScrollBar.Value + mouseDelta;
				this.MainScrollBar.Value = mouseDelta;
			});
		}
		void PropertyGridLabel_DoubleClick(object sender, RoutedEventArgs e)
		{
			this.selectedItem.ExecuteDefaultAction();
		}
		void MainBorder_SizeChanged(object sender, SizeChangedEventArgs e)
		{
			this.SetScrollBar();
			this.ResetViewPort();
		}
		void MainScrollBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
		{
			var newMargin = this.MainGrid.Margin;
			newMargin.Top = (e.NewValue * -1);
			this.MainGrid.Margin = newMargin;
		}
		void MainGrid_LayoutUpdated(object sender, EventArgs e)
		{
			if (DesignerProperties.IsInDesignTool)
				return;
			this.SetScrollBar();
		}
		void CategorizeButton_Click(object sender, RoutedEventArgs e)
		{
			isSorted = false;
			if (sortAZ)
			{
				sortAZ = false;
				this.CategorizeButton.IsChecked = true;
				this.SortButton.IsChecked = false;
				this.ToggleSort();
			}
			else
			{
				this.CategorizeButton.IsChecked = true;
			}


			if (null == this.selectedItem)
				this.ActivateItem(0);
			else
				this.selectedItem.Activate();

		}
		void SortButton_Click(object sender, RoutedEventArgs e)
		{
			isSorted = true;
			if (!sortAZ)
			{
				sortAZ = true;
				this.CategorizeButton.IsChecked = false;
				this.SortButton.IsChecked = true;
				this.ToggleSort();
			}
			else
			{
				this.SortButton.IsChecked = true;
			}

			if (null == this.selectedItem)
				this.ActivateItem(0);
			else
				this.selectedItem.Activate();

		}
		void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
		{
			this.FilterList(this.FilterTextBox.Text);
		}
		void FilterTextBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			var item = this.gridItems.FirstOrDefault(p => p.Value.IsVisible);
			if (null == item.Value)
				return;
			this.FilterTextBox.IsTabStop = true;
			this.FilterTextBox.FontStyle = FontStyles.Normal;
			this.FilterTextBox.Text = string.Empty;
			this.FilterTextBox.Foreground = new SolidColorBrush(KnownColor.Black);
			this.FilterTextBox.TextChanged += new TextChangedEventHandler(FilterTextBox_TextChanged);
			this.FilterTextBox.GotFocus += new RoutedEventHandler(FilterTextBox_GotFocus);
			this.FilterTextBox.Focus();
		}
		void FilterTextBox_GotFocus(object sender, RoutedEventArgs e)
		{
			this.FilterTextBox.GotFocus -= new RoutedEventHandler(FilterTextBox_GotFocus);
			this.FilterTextBox.Focus();
			this.FilterTextBox.LostFocus += new RoutedEventHandler(FilterTextBox_LostFocus);
		}
		void FilterTextBox_LostFocus(object sender, RoutedEventArgs e)
		{
			this.FilterTextBox.LostFocus -= new RoutedEventHandler(FilterTextBox_LostFocus);
			if (this.FilterTextBox.Text.Length == 0)
				this.ResetFilterTextBox();
		}

		void ClearButton_Click(object sender, RoutedEventArgs e)
		{
			this.FilterTextBox.Text = string.Empty;
			this.Dispatcher.BeginInvoke(() =>
				{
					this.ResetFilterTextBox();
					this.ActivateItem(0);
				});
		}
		#endregion
	}
	#endregion
}
